The Developer Fastlane

« 365 days to become a developer » challenge

PHP: Stats dashboard

November 14, 2020

Instructions & result

We want to create a dashboard that allow the user to check his/her website statistics.
  • We create the page dashboard.php
  • On the left of the page we have a sidebar that lists last 5 years
  • When we click on a year, a months sublist is displayed.
  • When we click on a month, the cumulated stats of this months is displayed on the right hand side of the dashboard. To do such a thing, we use the function glob()
Result
Click on one of the images above to access the exercise's website

Code

Steps

  1. Create the dashboard.php page structure using Boostrap
  2. Create the list of years and month (in the left menu of the page)
  3. Code the function to merge counters data (we reuse the files generated in the previous lesson)
    • hange the previous function to divide the count by day
    • Make a pattern using glob() function. For years, and for months.
    • Extract content from files of the list generated and make a sum of them
    • Display the result
  4. Optional: create a table for each month, listing the data per day.

dashboard.php

The page where the dashboard is displayed
Code
<?php

$title = "Dashboard";
require 'includes/header.php'; 
require 'includes/functions_analytics.php'; 
require_once 'includes/functions_counters.php'; // We need to reinclude it (in the footer is too late)
?>
<p class="lead mb-5">This dashboard lists all the statistics on the website since it was created. Click on years and months listed on the left to access the detailed data.</p>

<div class="row">

  <!-- MENU (left) -->

  <div class="col-md-4">
    <div class="list-group mb-4">
      <?php  
        for ($year = (int)date('Y'); $year >= (int)date('Y') - 5; $year--): ?>
          <a href="?year=<?= $year ?>" class="list-group-item <?= li_active('year', $year) ?>">
            <b><?= $year ?></b>
          </a><?php 
          if ( (int)$_GET['year'] === $year ): // List months
            if ($_GET['year'] !== date("Y")): $m = 12; else: $m = date('m'); endif; 
              // If clicked is the current year, then display ONLY past months
            for($i=$m;$i>0;$i--): ?>
              <a href="?year=<?= $year ?>&month=<?= $i ?>" class="list-group-item <?= li_active('month', $i) ?>">
                  <?= $breadcrumb[$year][] = date('F',strtotime('01.'.$i.'.2000')); // To list months in letters ?>
              </a><?php 
            endfor;
          endif;
        endfor; ?>
    </div>
  </div>

  <!-- DATA (right) -->

  <div class="col-md-8">
    <div class="card mb-3">

      <!-- Breadcrumb --> 

      <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
          <?php $breadcrumb = breadcrumb_dash(); ?>
            <li class="breadcrumb-item active"><?= $breadcrumb['total'] ?></li>
            <?php if (isset($_GET['year'])): ?>
              <li class="breadcrumb-item active"><?= $breadcrumb['year'] ?></li>
            <?php endif; ?>
            <?php if (isset($_GET['month'])): ?>
              <li class="breadcrumb-item active"><?= $breadcrumb['month'] ?></li>
            <?php endif; ?>
        </ol>
      </nav>

      <!-- Card (year & month)--> 

      <div class="card-body">
        <?php if (!isset($_GET['year'])): ?>
          <div class="row">
            <div class="col-sm mb-3">
                <h3><?= counter_sum('total'); ?></h3>Visited pages
            </div>
            <div class="col-sm">
                <h3><?= counter_sum(date("Y-m-d")); ?></h3>Today
            </div>
          </div>
        <?php elseif (!isset($_GET['month'])) : ?>
            <h3><?= counter_sum('year', $_GET['year']) ?></h3>Visited pages
        <?php endif; ?>
        <?php if (isset($_GET['month'])): ?>
            <h3><?= counter_sum('month', $_GET['year'], $_GET['month']) ?></h3>Visited pages
      </div>

    </div>

    <!-- Table (days) -->

    <div class="card">
      <table class="table">
        <thead class="thead-light">
            <tr>
              <th>Day</th>
              <th>Page views</th>
            </tr>
        </thead>
        <tbody>
          <?php if (counter_sum('month', $_GET['year'], $_GET['month']) > 0 ):
            for ($i=31; $i>0; $i--):
                $day_file = $_GET['year'] . '-' . str_pad($_GET['month'], 2, '0', STR_PAD_LEFT) . '-' . str_pad($i, 2, '0', STR_PAD_LEFT); 
                if (file_exists(counter_page_views_db($day_file))): ?>
                  <tr>
                    <td><?= $i ?></td>
                    <td><?= counter_page_views_int($day_file); ?></td>
                  </tr>
                <?php endif; ?>
            <?php endfor; ?>
          <?php else: ?>
            <tbody>
              <tr>
                <td colspan="2">No data</td>
              </tr>
            </tbody>
          <?php endif; ?>
        </tbody>
      </table>
    </div>

    <?php endif; ?>

  </div>
</div>

<?php require 'includes/footer.php'; ?>

footer.php

Code
            </div>
        </main><!-- /.container -->

        <!-- Footer -->
        <footer class="page-footer font-small mt-5">

            <!-- Footer Text -->
            <div class="container text-center text-md-left py-5">
                <div class="row">
                    <div class="col-md-4">
                    <h5>Pages list</h5>
                        <ul class="list-unstyled">
                            <?= nav_menu(); ?>
                        </ul>
                    </div>
                    <div class="col-md-4">
                        <h5>Stats</h5>
                        <ul class="list-unstyled">
                            <li>
                                <?php 
                                require_once (dirname(__DIR__) . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'functions_counters.php'); 
                                counter_page_views_increment('total');
                                counter_page_views_increment(date("Y-m-d"));
                                ?>
                                <!-- // Display counter snipet -->
        <b><?= counter_page_views_int('total') ?></b> page view<?php if (counter_page_views_int('total') > 1): ?>s<?php endif; ?> 
                            </li>
                        </ul>
                    </div>
                    <div class="col-md-4">
                        <h5>Admin</h5>
                        <ul class="list-unstyled">
                            <li>
                                <a href="dashboard.php">Statistics</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
</html>

functions_analytics.php

News functions created for the dashboard page.
Code
<?php 

function li_active(string $period, int $number): string
{
    $active = '';
    if ((int)$_GET[$period] === $number) {
        $active = "active";
    }
    return $active;
}
function counter_sum(string $period, int $year=2000, int $month=1): int
{
    $views = [];
    $month = str_pad($month, 2, '0', STR_PAD_LEFT); // To be sure the month are 2 digits in the filename
    if($period ==='year') {
        $search = "$year-*";
    } elseif($period ==='month') {
        $search = "$year-$month-*";
    } elseif($period === 'total') {
        $search = "*-*-*";
    } else {
        return null;
    }
    foreach (glob("data/counter_page-views/$search") as $file ) {
        $views[] = (int)file_get_contents($file);
    }
    return (int)array_sum($views);
}
function breadcrumb_dash(): array
{
    $breadcrumb['total'] = '5 last years';
    $breadcrumb['year'] = $_GET['year'];
    $breadcrumb['month'] = date('F',strtotime('01.'.$_GET['month'].'.2000'));
    if(isset($_GET['year'])) { 
        $breadcrumb['total'] = '<a href="dashboard.php">' . $breadcrumb['total'] . '</a>';
    }
    if(isset($_GET['month'])) { 
        $breadcrumb['year'] = '<a href="dashboard.php?year=' . $breadcrumb['year'] . '">' . $breadcrumb['year'] . '</a>';
    }
    return $breadcrumb;
}

functions_counter.php

Updated file created previously for counters in general
Code
<?php

// page views

function counter_page_views_db($file): string 
{
    $db = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'php-playground' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'counter_page-views' . DIRECTORY_SEPARATOR . $file;
    return $db;
}
function counter_page_views_increment($file): void
{
    if (strpos($_SERVER["SCRIPT_NAME"], 'dashboard.php') === false) { // Exclude admin pages
        $db = counter_page_views_db($file);
        if (file_exists($db)) {
            $views = (int)file_get_contents($db);
            $views++;
        } else {
            $views = 1;
        }
        file_put_contents($db, $views);
    }
} 
function counter_page_views_int($file): int
{
    $db = counter_page_views_db($file);
    return (int)file_get_contents($db);
}

style.css

Updated file created previously for counters in general
Code
.page-footer {
    background-color: #f3f3f3;
}
a:hover {
  text-decoration: none !important;
}
.breadcrumb {
  border-radius: .25rem .25rem 0 0;
}
.table thead th {
  border: none;
}
tbody > tr:first-child > * {
  border-top: none;
}
th {
  color: #6c757d !important;
  font-weight: 400;
}
@media (min-width: 576px) {
  .col-sm:nth-child(2) {
    border-left: solid 1px #dddddd !important;
  }
}
© 2020 - Edouard Proust | The Developer Fastlane